use crate::glutils::ShaderProgram;
use crate::glutils::{VertexArray, VertexLayout};
use crate::shaders;
use crate::camera::{Camera, Camera2D};

use super::texture::*;

use std::rc::Rc;

use zmath::{Rect, Mat4};

pub struct SpriteBatcher {
    vertices: Vec<f32>,
    indices: Vec<u32>,
    vao: VertexArray,
    texture: Rc<Texture>,
    shader: ShaderProgram,
    num_sprites: i32,
}

impl SpriteBatcher {
    pub fn new(extent: (f32, f32), texture: Rc<Texture>) -> Self {
        let shader = ShaderProgram::from_source(shaders::TEX_VERTEX_SHADER_SRC, shaders::TEX_FRAGMENT_SHADER_SRC);
        shader.set_uniform("proj", Mat4::orthogonal(-extent.0 / 2.0, extent.0 / 2.0, -extent.1 / 2.0, extent.1 / 2.0, 1.0, -1.0));
        Self {
            vertices: Vec::new(),
            indices: Vec::new(),
            vao: VertexArray::new(VertexLayout::P1UV),
            texture,
            shader,
            num_sprites: 0
        }
    }

    pub fn begin(&mut self) {
        self.vertices.clear();
        self.indices.clear();
        self.num_sprites = 0;
    }

    pub fn draw(&mut self, x: f32, y: f32, width: f32, height: f32) {
        self.draw_sprite(Rect { left: 0.0, bottom: 0.0, right: 1.0, top: 1.0 }, x, y, width, height)
    }

    pub fn draw_sprite(&mut self, region: Rect, x: f32, y: f32, width: f32, height: f32) {
        let hw = width / 2.0;
        let hh = height / 2.0;
        let x1 = x - hw;
        let y1 = y - hh;
        let x2 = x + hw;
        let y2 = y + hh;

        self.vertices.push(x1);
        self.vertices.push(y1);
        self.vertices.push(region.left);
        self.vertices.push(region.bottom);

        self.vertices.push(x2);
        self.vertices.push(y1);
        self.vertices.push(region.right);
        self.vertices.push(region.bottom);

        self.vertices.push(x2);
        self.vertices.push(y2);
        self.vertices.push(region.right);
        self.vertices.push(region.top);

        self.vertices.push(x1);
        self.vertices.push(y2);
        self.vertices.push(region.left);
        self.vertices.push(region.top);

        let start = self.num_sprites as u32 * 4;

        self.indices.push(start + 0);
        self.indices.push(start + 1);
        self.indices.push(start + 2);

        self.indices.push(start + 2);
        self.indices.push(start + 3);
        self.indices.push(start + 0);

        self.num_sprites += 1;
    }

    pub fn present(&mut self, camera: Option<&Camera2D>) {
        self.vao.set_triangles(&self.vertices, &self.indices);


        self.shader.use_program();
        if let Some(camera) = camera {
            self.shader.set_uniform("trans", camera.mat());
        } else {
            self.shader.set_uniform("trans", Mat4::identity());
        }
        self.texture.bind(0);
        self.shader.set_uniform("tex", 0);
        self.vao.bind();
        self.vao.draw(gl::TRIANGLES, self.num_sprites * 6);
        self.vao.unbind();
        self.texture.unbind();
    }

    pub fn shader(&self) -> &ShaderProgram {
        &self.shader
    }
}